#include "stdafx.h"
#include "adodb_impl.h"
#include <sstream>
#include <strsafe.h>
#include <time.h>

#define MAX_PARAMETER_COUNT	2100

bool ADODB::Impl::com_init_ = false;

ADODB::Impl::Impl() : connection_(NULL), command_(NULL), command_type_(adCmdUnspecified), recordset_(NULL), parameters_(NULL), fields_(NULL)
{
	output_parameters_.reserve(MAX_PARAMETER_COUNT);
	ZeroMemory(&timestamp_, sizeof(timestamp_));
}

ADODB::Impl::~Impl()
{
	Disconnect();

	if (com_init_ == true) {
		CoUninitialize();
	}
	output_parameters_.clear();
}

bool ADODB::Impl::Connect(const wchar_t* ip, const wchar_t* db, const wchar_t* user, const wchar_t* password, unsigned long timeout)
{
	std::wostringstream connection_stream;
	connection_stream << "Provider=SQLOLEDB; Data Source = " << ip << "; Initial Catalog = " << db << "; User ID = " << user << "; Password = " << password;
	std::wstring connection_string(connection_stream.str());
	return Connect(connection_string.c_str(), timeout);
}

bool ADODB::Impl::Connect(const wchar_t* connection_string, unsigned long timeout)
{
	HRESULT hr = S_FALSE;

	if (com_init_ == false)
	{
		hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_SPEED_OVER_MEMORY);
		if (FAILED(hr)) {
			return false;
		}
		com_init_ = true;
	}

	hr = CoCreateInstance(__uuidof(Connection), NULL, CLSCTX_INPROC, __uuidof(_Connection), reinterpret_cast<void**>(&connection_));
	if (FAILED(hr)) {
		return false;
	}

	hr = CoCreateInstance(__uuidof(Command), NULL, CLSCTX_INPROC, __uuidof(_Command), reinterpret_cast<void**>(&command_));
	if (FAILED(hr)) {
		return false;
	}

	if (timeout) {
		hr = connection_->put_ConnectionTimeout(timeout);
		if (FAILED(hr)) {
			return false;
		}
	}

	hr = connection_->put_ConnectionString(_bstr_t(connection_string));
	if (FAILED(hr)) {
		return false;
	}

	hr = connection_->Open(_bstr_t(""), _bstr_t(""), _bstr_t(""), NULL);
	if (FAILED(hr)) {
		return false;
	}
	
	hr = command_->putref_ActiveConnection(connection_);
	if (FAILED(hr)) {
		return false;
	}

	hr = command_->get_Parameters(&parameters_);
	if (FAILED(hr)) {
		return false;
	}
	parameters_->Release();
	
	return true;
}

bool ADODB::Impl::Disconnect()
{
	if (connection_ != nullptr)
	{
		HRESULT hr = S_OK;

		if (recordset_ != nullptr)
		{
			if (fields_ != nullptr)
			{
				long count = 0;			
				hr = fields_->get_Count(&count);
				if (FAILED(hr))
					return false;
			
				if (count > 0)
				{
					hr = recordset_->Close();
					if (FAILED(hr))
						return false;
				}
			}

			recordset_->Release();
			recordset_ = nullptr;
		}

		if (command_ != nullptr)
		{
			command_->Release();
			command_ = nullptr;
		}

		hr = connection_->Close();
		if (FAILED(hr))
			return false;
		
		connection_->Release();
		connection_ = nullptr;
	}

	return true;
}

bool ADODB::Impl::SetCommand(CommandType type, const wchar_t* sql, unsigned long timeout)
{
	long count = 0;
	HRESULT hr = parameters_->get_Count(&count);
	if (FAILED(hr)) {
		return false;
	}

	if (count > 0) {
		variant_t column(static_cast<short>(0));
		do {
			_Parameter* paramter = nullptr;
			hr = parameters_->get_Item(column, &paramter);
			if (FAILED(hr)) {
				return false;
			}
			paramter->Release();

			VARIANT value;
			hr = paramter->get_Value(&value);
			if (FAILED(hr)) {
				return false;
			}

			switch (value.vt)
			{
			case VT_BSTR:
				SysFreeString(value.bstrVal);
				break;
			case VT_ARRAY | VT_UI1:
				SafeArrayDestroy(value.parray);
				break;
			}

			hr = parameters_->Delete(column);
			if (FAILED(hr)) {
				return false;
			}
		} while (--count);
	}

	if (output_parameters_.empty() == false)
	{
		for each (auto output_parameter in output_parameters_)
			delete output_parameter;

		output_parameters_.clear();
	}

	hr = command_->put_CommandText(_bstr_t(sql));
	if (FAILED(hr)) {
		return false;
	}

	if (timeout > 0) {
		hr = command_->put_CommandTimeout(timeout);
		if (HRESULT(hr)) {
			return false;
		}
	}

	switch (type) {
	case CommandText:
		command_type_ = adCmdText;
		break;
	case CommandSP:
		command_type_ = adCmdStoredProc;
		break;
	}

	return true;
}

bool ADODB::Impl::SetParameter(_variant_t column, ParameterDirection direction, DataType type, void* buffer, std::size_t size)
{
	if (command_ == NULL) {
		return false;
	}

	if (parameters_ == NULL) {
		return false;
	}

	std::size_t buffer_size = size;

	DataTypeEnum adtype = adEmpty;
	_variant_t variant;
	switch (type) {
	case DataTinyInt:
		adtype = adTinyInt;
		if (buffer != NULL) {
			variant = *static_cast<char*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedTinyInt:
		adtype = adUnsignedTinyInt;
		if (buffer != NULL) {
			variant = *static_cast<unsigned char*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataSmallInt:
		adtype = adSmallInt;
		if (buffer != NULL) {
			variant = *static_cast<short*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedSmallInt:
		adtype = adUnsignedSmallInt;
		if (buffer != NULL) {
			variant = *static_cast<unsigned short*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataInt:
		adtype = adInteger;
		if (buffer != NULL) {
			variant = *static_cast<int*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedInt:
		adtype = adInteger;
		if (buffer != NULL) {
			variant = *static_cast<unsigned int*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataBigInt:
		adtype = adBigInt;
		if (buffer != NULL) {
			variant = *static_cast<__int64*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataUnsignedBigInt:
		adtype = adUnsignedBigInt;
		if (buffer != NULL) {
			variant = *static_cast<unsigned __int64*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataFloat:
		adtype = adSingle;
		if (buffer != NULL) {
			variant = *static_cast<float*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataDouble:
		adtype = adDouble;
		if (buffer != NULL) {
			variant = *static_cast<double*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataChar:
		adtype = adChar;
		variant = static_cast<const char*>(buffer);
		if (buffer != NULL) {
			variant = static_cast<const char*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataVarChar:
		adtype = adVarChar;
		variant = static_cast<const char*>(buffer);
		if (buffer != NULL) {
			variant = static_cast<const char*>(buffer);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataWChar:
		adtype = adWChar;
		variant = static_cast<const wchar_t*>(buffer);
		if (buffer != NULL) {
			variant = static_cast<const wchar_t*>(buffer);
			buffer_size *= sizeof(wchar_t);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataVarWChar:
		adtype = adVarWChar;
		if (buffer != NULL) {
			variant = static_cast<const wchar_t*>(buffer);
			buffer_size *= sizeof(wchar_t);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataLongVarWChar:
		adtype = adLongVarWChar;
		if (buffer != NULL) {
			variant = static_cast<const wchar_t*>(buffer);
			buffer_size *= sizeof(wchar_t);
		} else {
			variant.ChangeType(VT_NULL);
		}
		break;
	case DataSmallDateTime:
	case DataDateTime:
		{
			adtype = adDBTimeStamp;
			if (buffer != NULL) {
				TIMESTAMP_STRUCT* ts = static_cast<TIMESTAMP_STRUCT*>(buffer);
				SYSTEMTIME system_time;
				system_time.wYear = ts->year;
				system_time.wMonth = ts->month;
				system_time.wDayOfWeek = 0;
				system_time.wDay = ts->day;
				system_time.wHour = ts->hour;
				system_time.wMinute = ts->minute;
				system_time.wSecond = ts->second;
				system_time.wMilliseconds = 0;
				double time = 0;
				SystemTimeToVariantTime(&system_time, &time);
				variant = time;
			} else {
				variant.ChangeType(VT_NULL);
				buffer_size = sizeof(DATE);
			}
			break;
		}
	case DataTimeStamp:
		{
			adtype = adBinary;
			SAFEARRAYBOUND bound[1];
			bound[0].cElements = 8;
			bound[0].lLbound = 0;
			variant.parray = SafeArrayCreate(VT_UI1, 1, bound);
		
			void* data = 0;
			HRESULT hr = SafeArrayAccessData(variant.parray, &data);
			if (FAILED(hr)) {
				return false;
			}

			memcpy(data, buffer, 8);

			hr = SafeArrayAccessData(variant.parray, &data);
			if (FAILED(hr)) {
				return false;
			}

			variant.vt = VT_ARRAY | VT_UI1;
			break;
		}
	default:
		return false;
	}

	column.ChangeType(VT_BSTR);

	_Parameter* parameter = NULL;
	HRESULT hr = command_->CreateParameter(column.bstrVal, adtype, static_cast<ParameterDirectionEnum>(direction + 1), (long)size, variant, &parameter);
	if (FAILED(hr)) {
		SysFreeString(column.bstrVal);
		return false;
	}

	hr = parameters_->Append(parameter);
	if (FAILED(hr)) {
		SysFreeString(column.bstrVal);
		return false;
	}
	parameter->Release();

	if (direction & ParameterOutput) {
		output_parameters_.push_back(new OutputParameter(column, type, buffer, buffer_size));
	}
	
	return true;
}

bool ADODB::Impl::ExecuteCommand()
{
	HRESULT hr = S_OK;

	if (recordset_ != nullptr)
	{
		if (fields_ != nullptr)
		{
			long count = 0;
			hr = fields_->get_Count(&count);
			if (FAILED(hr))
				return false;

			if (count > 0)
			{
				hr = recordset_->Close();
				if (FAILED(hr))
					return false;
			}

			fields_ = nullptr;
		}

		recordset_->Release();
		recordset_ = nullptr;
	}

	CursorLocationEnum current_cursor_location = adUseNone;
	hr = connection_->get_CursorLocation(&current_cursor_location);
	if (FAILED(hr))
		return false;
	
	CursorLocationEnum selected_cursor_location = adUseServer;

	if (!output_parameters_.empty())
	{
		selected_cursor_location = adUseClient;
	}

	if (current_cursor_location != selected_cursor_location)
	{
		hr = connection_->put_CursorLocation(selected_cursor_location);
		if (FAILED(hr))
			return false;
	}

	hr = CoCreateInstance(__uuidof(Recordset), NULL, CLSCTX_INPROC, __uuidof(_Recordset), reinterpret_cast<void**>(&recordset_));
	if (FAILED(hr))
		return false;

	hr = recordset_->putref_Source(command_);
	if (FAILED(hr))
		return false;

	hr = recordset_->Open(vtMissing, vtMissing, adOpenForwardOnly, adLockReadOnly, command_type_);
	if (FAILED(hr))
		return false;

	hr = recordset_->get_Fields(&fields_);
	if (FAILED(hr))
		return false;

	fields_->Release();

	if (selected_cursor_location == adUseClient)
	{
		for each(OutputParameter* output_parameter in output_parameters_)
		{
			if (!GetParameterData(output_parameter->column(), output_parameter->type(), output_parameter->buffer(), output_parameter->buffer_size(), NULL))
				return false;

			delete output_parameter;
		}
		output_parameters_.clear();
	}

	return true;
}

bool ADODB::Impl::GetRowCount(unsigned long& count)
{
	if (recordset_ == NULL) {
		return false;
	}

	for (count = 0; SUCCEEDED(recordset_->MoveNext()); ++count);

	if (count > 0) {
		HRESULT hr = recordset_->MoveFirst();
		if (FAILED(hr)) {
			return false;
		}
	}

	return true;
}

bool ADODB::Impl::GetRowSize(std::size_t& size)
{
	if (fields_ == NULL) {
		return false;
	}

	short column_count = 0;
	if (!GetColumnCount(column_count)) {
		return false;
	}

	long actual_size = 0;
	for (short count = 0; count < column_count; ++count) {
		Field* field = NULL;
		_variant_t column_variant(static_cast<short>(count));
		HRESULT hr = fields_->get_Item(column_variant, &field);
		if (FAILED(hr))	{
			return false;
		}
		field->Release();

		hr = field->get_ActualSize(&actual_size);
		if (FAILED(hr))	{
			return false;
		}
		size += actual_size;
	}

	return true;
}

bool ADODB::Impl::GetColumnCount(short& count)
{
	if (fields_ == NULL) {
		return false;
	}

	long temp_count = 0;
	HRESULT hr = fields_->get_Count(&temp_count);
	if (FAILED(hr)) {
		return false;
	}
	count = static_cast<short>(temp_count);

	return true;
}

bool ADODB::Impl::GetColumnType(const _variant_t& column, DataType& type)
{
	if (fields_ == NULL) {
		return false;
	}

	Field* field = NULL;
	HRESULT hr = fields_->get_Item(column, &field);
	if (FAILED(hr)) {
		return false;
	}
	field->Release();

	DataTypeEnum adtype;
	hr = field->get_Type(&adtype);
	if (FAILED(hr)) {
		return false;
	}

	switch (adtype) {
	case adTinyInt:
		type = DataTinyInt;
		break;
	case adUnsignedTinyInt:
		type = DataUnsignedTinyInt;
		break;
	case adSmallInt:
		type = DataSmallInt;
		break;
	case adUnsignedSmallInt:
		type = DataUnsignedSmallInt;
		break;
	case adInteger:
		type = DataInt;
		break;
	case adUnsignedInt:
		type = DataUnsignedInt;
		break;
	case adBigInt:
		type = DataBigInt;
		break;
	case adUnsignedBigInt:
		type = DataUnsignedBigInt;
		break;
	case adSingle:
		type = DataFloat;
		break;
	case adDouble:
		type = DataDouble;
		break;
	case adChar:
		type = DataChar;
		break;
	case adVarChar:
		type = DataVarChar;
		break;
	case adWChar:
		type = DataWChar;
		break;
	case adVarWChar:
		type = DataVarWChar;
		break;
	case adDBTimeStamp:
		type = DataTimeStamp;
		break;
	case adBinary:
		type = DataBinary;
		break;
	}
	
	return true;
}

bool ADODB::Impl::GetColumnActualSize(const _variant_t& column, std::size_t& size)
{
	if (fields_ == NULL) {
		return false;
	}

	Field* field = NULL;
	HRESULT hr = fields_->get_Item(column, &field);
	if (FAILED(hr))	{
		return false;
	}
	field->Release();

	long temp_size = 0;
	hr = field->get_ActualSize(&temp_size);
	if (FAILED(hr))	{
		return false;
	}
	size = temp_size;

	return true;
}

bool ADODB::Impl::GetColumnDefinedSize(const _variant_t& column, std::size_t& size)
{
	if (fields_ == NULL) {
		return false;
	}

	Field* field = NULL;
	HRESULT hr = fields_->get_Item(column, &field);
	if (FAILED(hr))	{
		return false;
	}
	field->Release();

	long temp_size = 0;
	hr = field->get_DefinedSize(&temp_size);
	if (FAILED(hr))	{
		return false;
	}
	size = temp_size;

	return true;
}

bool ADODB::Impl::GetColumnData(const _variant_t& column, DataType type, void* buffer, std::size_t buffer_size, std::size_t* actual_size)
{
	if (fields_ == NULL) {
		return false;
	}

	Field* field = NULL;
	HRESULT hr = fields_->get_Item(column, &field);
	if (FAILED(hr)) {
		return false;
	}
	field->Release();

	_variant_t variant(vtMissing);
	hr = field->get_Value(&variant);
	if (FAILED(hr)) {
		return false;
	}
	
	if (GetVariantData(variant, buffer, buffer_size,actual_size) == false)
		return false;

	return true;
}

bool ADODB::Impl::IsRowEnd()
{
	if (recordset_ == NULL) {
		return false;
	}

	VARIANT_BOOL eof = FALSE;
	HRESULT hr = recordset_->get_EOF(&eof);
	if (SUCCEEDED(hr) && !eof) {
		return false;
	}

	return true;
}

bool ADODB::Impl::MoveNextRow()
{
	if (recordset_ == NULL) {
		return false;
	}

	HRESULT hr = recordset_->MoveNext();
	if (FAILED(hr)) {
		return false;
	}

	return true;
}

bool ADODB::Impl::IsConnected()
{
	if (connection_ == nullptr)
		return false;

	long state = 0;
	HRESULT hr = connection_->get_State(&state);
	if (FAILED(hr))
		return false;

	if (state == adStateClosed)
		return false;

	return true;
}

bool ADODB::Impl::GetParameterData(const _variant_t& column, DataType type, void* buffer, std::size_t buffer_size, std::size_t* actual_size)
{
	if (parameters_ == NULL) {
		return false;
	}

	_Parameter* parameter = NULL;
	HRESULT hr = parameters_->get_Item(column, &parameter);
	if (FAILED(hr)) {
		return false;
	}
	parameter->Release();

	_variant_t variant(vtMissing);
	hr = parameter->get_Value(&variant);
	if (FAILED(hr)) {
		return false;
	}

	if (!GetVariantData(variant, buffer, buffer_size, actual_size)) {
		return false;
	}

	return true;
}

bool ADODB::Impl::GetVariantData(_variant_t& variant, void* buffer, std::size_t buffer_size, size_t* actual_size)
{
	if (variant.vt == VT_NULL) {
		if (actual_size != nullptr) {
			*actual_size = NULL_DATA;
		}
	} else {
		void* data = NULL;
		std::size_t size = 0;
		switch (variant.vt) {
		case VT_EMPTY:
			return true;
		case VT_I1:
			data = &variant.cVal;
			size = sizeof(variant.cVal);
			break;
		case VT_UI1:
			data = &variant.bVal;
			size = sizeof(variant.bVal);
			break;
		case VT_I2:
			data = &variant.iVal;
			size = sizeof(variant.iVal);
			break;
		case VT_UI2:
			data = &variant.uiVal;
			size = sizeof(variant.uiVal);
			break;
		case VT_I4:
			data = &variant.intVal;
			size = sizeof(variant.intVal);
			break;
		case VT_UI4:
			data = &variant.uintVal;
			size = sizeof(variant.uintVal);
			break;
		case VT_I8:
			data = &variant.llVal;
			size = sizeof(variant.llVal);
			break;
		case VT_UI8:
			data = &variant.ullVal;
			size = sizeof(variant.ullVal);
			break;
		case VT_R4:
			data = &variant.fltVal;
			size = sizeof(variant.fltVal);
			break;
		case VT_R8:
		case VT_DECIMAL:
			data = &variant.decVal.Lo64;
			//data = &variant.dblVal;
			size = sizeof(variant.decVal.Lo64);
			break;
		case VT_BSTR:
			{
				std::size_t length = 0;
				HRESULT hr = StringCbLengthW(variant.bstrVal, buffer_size, &length);
				SysFreeString(variant.bstrVal);
				if (FAILED(hr)) {
					return false;
				}
				data = variant.bstrVal;
				size = length;
				break;
			}
		case VT_DATE:
			SYSTEMTIME systime;
			VariantTimeToSystemTime(variant.date, &systime);
			timestamp_.year = systime.wYear;
			timestamp_.month = systime.wMonth;
			timestamp_.day = systime.wDay;
			timestamp_.hour = systime.wHour;
			timestamp_.minute = systime.wMinute;
			timestamp_.second = systime.wSecond;
			timestamp_.fraction = systime.wMilliseconds * 1000000; 
			data = &timestamp_;
			size = sizeof(TIMESTAMP_STRUCT);
			break;
		case VT_ARRAY | VT_UI1:
			{
				HRESULT hr = SafeArrayAccessData(variant.parray, &data);
				if (FAILED(hr)) {
					return false;
				}

				size = variant.parray->rgsabound->cElements;

				__int64 timestamp = 0;	
				for (unsigned long offset = 0; --size; ++offset) {
					reinterpret_cast<BYTE*>(&timestamp)[offset] = reinterpret_cast<BYTE*>(data)[size];
				}

				hr = SafeArrayUnaccessData(variant.parray);
				if (FAILED(hr)) {
					return false;
				}

				hr = SafeArrayDestroy(variant.parray);
				if (FAILED(hr)) {
					return false;
				}

				tm time;
				errno_t error = localtime_s(&time, &timestamp);
				if (error != NO_ERROR) {
					return false;
				}
				
				timestamp_.year = static_cast<SQLSMALLINT>(time.tm_year + 1900);
				timestamp_.month = static_cast<SQLSMALLINT>(time.tm_mon + 1);
				timestamp_.day = static_cast<SQLSMALLINT>(time.tm_mday);
				timestamp_.hour = static_cast<SQLSMALLINT>(time.tm_hour);
				timestamp_.minute = static_cast<SQLSMALLINT>(time.tm_min);
				timestamp_.second = static_cast<SQLSMALLINT>(time.tm_sec);
				timestamp_.fraction = 0;

				data = &timestamp_;
				size = sizeof(TIMESTAMP_STRUCT);
				break;
			}
		default:
			return false;
		}

		if (size > buffer_size) {
			size = buffer_size;
		}

		memcpy(buffer, data, size);

		if (actual_size != NULL) {
			*actual_size = size;
		}
	}

	return true;
}

bool ADODB::Impl::SetCursorLocation(CursorLocation location)
{
	HRESULT hr = connection_->put_CursorLocation(static_cast<CursorLocationEnum>(location + adUseServer));
	if (FAILED(hr)) {
		return false;
	}

	return true;
}